import collections

from django.contrib.auth.decorators import login_required
from django.contrib.auth import login as auth_login, logout as auth_logout, \
    authenticate
from django.db.models import Q, Count
from django.shortcuts import render, redirect, get_object_or_404, get_list_or_404
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.template import RequestContext
from django.contrib.auth.models import Group, Permission
from django.core.exceptions import FieldError
from django.contrib.auth import get_user_model

from engonline.models import UserAnswer, TextArtefact
from page.models import Page, Tracked
from test.models import Test, ProgressCheck, DiagnosticTest
from utils import paginate, calculate_score
from account.decorators import staff_required
from page.utils import get_active_diagnostic_test
from page.tree_utils import is_page_under
from keelback.text_identifiers import PROGRESS, IDP_LOGOUT


def login(request):
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST['password']
        user = authenticate(username=username, password=password)
        
        if user is not None:
            auth_login(request, user)
            return redirect('engonline.views.home')
        else:
            return render(request, 'login.html', {'login_failure': True})
    else:
        return render(request, 'login.html')

@login_required
def logout(request):
    
    user = request.user
    auth_logout(request)

    if user.is_local:
        return redirect('engonline.views.home')
    else:
        # Get the idp logout text
        try:
            idp_logout_text = TextArtefact.objects.get(name=IDP_LOGOUT).html
        except Exception, e:
            idp_logout_text = "Error"

        template_vars = {'idp_logout_text': idp_logout_text}   

        return render(request, 'IDP_logout.html', template_vars)

    

@login_required
@staff_required
def view_group(request, group, page_number=1):
    """
    Shows details for a single group.
    """
    raise NotImplementedError

@login_required
@staff_required
def list_groups(request):
    """
    Lists all groups (paginated).
    The list is filtered on name if there's a 'search' GET-variable.
    """
    groups_per_page = 10
    
    #Get GET-variables
    page_number = request.GET.get('page')
    filter_string = request.GET.get('search')

    #Init query-components
    query_text = Q()

    if filter_string is not None and len(filter_string) > 0:
        query_text = Q(name__icontains=filter_string)

    #Get all groups with number of members
    try:
        groups = Group.objects.filter(query_text).annotate(number_of_members=Count('user'))
    except FieldError, e:
        groups = Group.objects.filter(query_text) #can't count members, return without
    except Exception, e:
        raise e

    #Paginate and render
    pageinated_groups = paginate(groups, groups_per_page, page_number)
    return render(request, 'list_groups.html', {'groups': pageinated_groups, 'filter_string': filter_string})

@login_required
@staff_required
def view_user(request, id):
    return _view_user(request, id)

@login_required
def my_profile(request):
    """
    Shows the logged in user's info and their test results
    """
    return _view_user(request, request.user.id)

def _view_user(request, id):
    """
    Shows the given user's info and their test results
    """
    test_results_per_page = 10
    page_number = request.GET.get('page')

    #Get user
    user = get_object_or_404(get_user_model(), id=id)
    
    template_vars = {
        'show_my_progress': False,
        'viewed_user': user,
    }

    test_results = _calculate_user_progress(request, user)
    template_vars.update(test_results)

    return render(request, 'progress.html', template_vars)

@login_required
@staff_required
def list_users(request):
    """
    Lists the users (paginated).
    The users are filtered on a search-string and/or a specific group. 
    These conditions are retrieved through GET-variables.
    """
    users_per_page = 10

    #Get GET-variables
    page_number = request.GET.get('page')
    filter_string = request.GET.get('search')
    filter_group = request.GET.get('group')

    #Init query-components
    query_text = Q()
    query_group = Q()

    #If filtering with string
    if filter_string is not None and len(filter_string) > 0:
        query_text = Q(Q(first_name__icontains=filter_string) | 
                Q(last_name__icontains=filter_string))

    #If filtering with group
    if filter_group is not None and len(filter_group) > 0:
        try:
            #Make sure group exists before filtering
            Group.objects.get(name__iexact=filter_group)
            query_group = Q(groups__name__iexact=filter_group)
        except Exception, e:
            pass #Group.object.get failed - do nothing

    #Get users
    users = get_user_model().objects.filter(Q(query_text & query_group), is_active=True)

    #Get available groups
    groups = Group.objects.all()

    #Paginate and render
    pageinated_users = paginate(users, users_per_page, page_number)
    return render(request, 'list_users.html', {'users': pageinated_users, 'groups': groups, 'filter_string': filter_string, 'filter_group': filter_group})
    
@login_required
@staff_required
def create_group(request):
    """
    Creates a group.
    """ 
    required_fields = False
    name_unavailable = False
    
    if request.method == 'POST':
        
        #read forms
        name = request.POST['inputName']
        # chosen_permissions = request.POST.getlist('inputPermissions[]')
        
         #check data
        if name == "":
            required_fields = True
        if Group.objects.filter(name=name).exists():
            name_unavailable = True
            
        #if one or more error is detected
        if(required_fields or name_unavailable):
        
                        return render(request,'create_group.html', {'required_fields': required_fields,
                       'name_unavailable': name_unavailable,})
        #create the group
        else:
            group = Group(name=name)
            group.save()
            
            # for permission in chosen_permissions:
            #     group.permissions.add(permission)
            
            group.save()
            request.session['group_saved'] = True
            return HttpResponseRedirect("/auth/edit-group/"+str(group.id)+"/")
    else:
        return render(request, 'create_group.html')

@login_required    
@staff_required
def edit_group(request, group_id):
    """
    Edit a group.
    """
    required_fields = False
    name_unavailable = False
    group = get_object_or_404(Group, id=group_id)
    
    if request.method == 'POST':
        
        #read forms
        new_name = request.POST['inputName']
        # chosen_permissions = request.POST.getlist('inputPermissions[]')
        
        #check data
        if new_name == "":
            required_fields = True
        if Group.objects.filter(name=new_name).exists() and new_name != group.name:
            name_unavailable = True
            
        #if one or more error is detected
        if(required_fields or name_unavailable):
        
                        return render(request,'create_group.html', {'required_fields': required_fields,
                       'name_unavailable': name_unavailable, 'edit_group':group})
        #edit the group
        else:
            group.name = new_name
            # group.permissions.clear()

            # for permission in chosen_permissions:
            #     group.permissions.add(permission)
            
            group.save()
            request.session['group_saved'] = True
            return render(request,'create_group.html', {'group_saved': True, 'edit_group':group})
    else:
        group_saved = request.session.get('group_saved', False)
        request.session['group_saved'] = False
        return render(request,'create_group.html', {'group_saved': group_saved, 'edit_group':group})

@login_required    
@staff_required
def delete_group(request, group_id):
    """
    Deletes a group.
    """
    group = get_object_or_404(Group, id=group_id)
    group.delete()
    
    return redirect('account.views.list_users')

@login_required    
@staff_required
def create_user(request):
    """
    Create a user.
    """
    required_fields = False
    username_unavailable = False
    password_diff = False
    
    if request.method == 'POST':
        
        #read forms
        username = request.POST['inputUsername']
        email = request.POST['inputEmail']
        password = request.POST['inputPassword']
        password2 = request.POST['inputPassword2']
        firstname = request.POST['inputFirstname']
        lastname = request.POST['inputLastname']
        is_staff = request.POST.get('inputStaff', False)
        is_super = request.POST.get('inputSuper', False)
        chosen_groups = request.POST.getlist('inputGroups[]')
        # chosen_permissions = request.POST.getlist('inputPermissions[]')
        
         #check data
        if username == "" or password == "" or firstname == "" or lastname == "":
            required_fields = True
        if get_user_model().objects.filter(external_identifier=username).exists():
            username_unavailable = True
        if password != password2:
            password_diff = True
            
        #if one or more error is detected
        if(required_fields or username_unavailable or password_diff):
        
                        return render(request,'create_user.html', {'required_fields': required_fields,
                       'username_unavailable': username_unavailable, 'password_diff': password_diff})
        #creates the user
        else:
            user = get_user_model().objects.create_user(username, password)
            user.first_name = firstname
            user.last_name = lastname
            user.email = email
            user.is_staff = is_staff
            user.is_superuser = is_super
            
            for group in chosen_groups:
                user.groups.add(group)
                
            # for permission in chosen_permissions:
            #     user.user_permissions.add(permission)
            
            user.save()
            return redirect('account.views.list_users')
    
    else:
        return render(request, 'create_user.html')
    
@login_required    
@staff_required
def edit_user(request, id):
    """
    Edit user.
    """
    user = get_object_or_404(get_user_model(), id=id)    
    
    required_fields = False
    password_diff = False
    
    if request.method == 'POST':
        
        if user.is_local:
            #check data
            if request.POST['inputFirstname'] == "" or request.POST['inputLastname'] == "":
                required_fields = True
            if request.POST['inputPassword'] != request.POST['inputPassword2']:
                password_diff = True
            
            if(required_fields or password_diff):
            
                return render(request,'create_user.html', {'edit_user': user, 'required_fields': required_fields,
                'password_diff': password_diff})
            else:
                user.first_name = request.POST['inputFirstname']
                user.last_name = request.POST['inputLastname']
                user.email = request.POST['inputEmail']
                
                if request.POST['inputPassword'] != "":
                    user.set_password(request.POST['inputPassword'])
                    user.save()
                        
        _set_attributes(request, user)
        return redirect('account.views.list_users')

    else:
        return render(request, 'create_user.html',{'edit_user': user})

  
def _set_attributes(request, user):
    """
    Sets attributes on user.
    """
    chosen_groups = request.POST.getlist('inputGroups[]')
    # chosen_permissions = request.POST.getlist('inputPermissions[]')
    
    user.is_staff = request.POST.get('inputStaff', False)
    user.is_superuser = request.POST.get('inputSuper', False)
    
    user.groups.clear()
    
    for group in chosen_groups:
            user.groups.add(group)
        
    # user.user_permissions.clear()

    # for permission in chosen_permissions:
    #     user.user_permissions.add(permission)
    
    user.save()

@login_required    
@staff_required
def trash_user(request, id):
    """
    Trashs user.
    """
    user = get_object_or_404(get_user_model(), id=id)
    user.is_active = False
    user.save()
    return redirect('account.views.list_users')

@login_required    
@staff_required
def delete_user(request, id):
    """
    Deletes user if it is not the creator or changer of any page.
    """
    user = get_object_or_404(get_user_model(), id=id)
    #Delete all answers
    UserAnswer.objects.filter(user=user).delete()
    #if the user has created any pages, set active to false
    if Page.objects.filter(creator=user).exists() or Page.objects.filter(changer=user).exists():
        user.is_active = False
        user.save()
    else:
        user.delete()
    
    return redirect('account.views.list_users')

@login_required
def my_progress(request):
    """
    Display my progress
    """
    # Get the progress text
    try:
        my_progress_text = TextArtefact.objects.get(name=PROGRESS)
    except Exception, e:
        my_progress_text = "Error"

    template_vars = {
        'show_my_progress': True,
        'viewed_user': request.user,
        'my_progress_text': my_progress_text,
    }

    test_results = _calculate_user_progress(request, request.user)
    template_vars.update(test_results)


    return render(request, 'progress.html', template_vars)

def _calculate_user_progress(request, user):
    """
    Calculates all test results and my progress.
    """

    test_results_per_page = 10
    page_number = request.GET.get('page')


    #Get all the users question answers (order by date, 1 test session will have the same time timestamp)
    user_answers = UserAnswer.objects.filter(user=user).order_by('-date')
    user_answers = filter(lambda a: a.test.type() is ProgressCheck or a.test.type() is DiagnosticTest, user_answers )
    
    test_results = []
    currentTest = None
    i = 0
    for answer in user_answers:
        if currentTest is None:
            #First iteration, no test yet, append
            test_results.append({'title': answer.test.title, 'correct': 0, 'total': 0, 'date': answer.date})
        elif currentTest != answer.date:
            #New Test, calculate last tests score
            test_results[i]['score'] = calculate_score(test_results[i]['correct'], test_results[i]['total'])

            #Append new test and increase index
            test_results.append({'title': answer.test.title, 'correct': 0, 'total': 0, 'date': answer.date})
            i += 1

        #Check if correct
        if answer.question.check_answer(answer.answer):
            test_results[i]['correct'] += 1

        #Up total and store current test date
        test_results[i]['total'] += 1
        currentTest = answer.date
    else:
        #If there are answers, calculate the last test as well
        if test_results.__len__() > 0:
            test_results[i]['score'] = calculate_score(test_results[i]['correct'], test_results[i]['total'])

    test_results = paginate(test_results, test_results_per_page, page_number)

    #Try to get the latest diagnostic test
    try:
        test=get_active_diagnostic_test()
        date = UserAnswer.objects.filter(user=request.user, test=test).latest('date').date
        results = _calculate_tracked_result(user, test, date)
    except UserAnswer.DoesNotExist:
        results = None

    # Return results
    return {
        'test_results': test_results,
        'results': results,
    }

def _calculate_page_result(user, test, date):
    """
    Caclutate a result for every page that is related to the test's questions 
    """

    #Get all user answers
    us = UserAnswer.objects.filter(test=test, date=date, user=user)
    pages = []
    correct=collections.defaultdict(int)
    total=collections.defaultdict(int)
    for u in us:
        if u.question.page is not None:
            page = u.question.page.id
            #Get all pages that is related to a question
            if page not in pages:
                pages.append(page)
            
            total[page] += 1
            if u.question.check_answer(u.answer):
                correct[page] += 1

        
    result = []
    for page in pages:
        result.append({'correct':correct[page], 'total':total[page], 'page':page})

    return result

def _calculate_tracked_result(user, test, date):
    """
    Calculate tracked result
    """
    
    total=collections.defaultdict(long)
    correct=collections.defaultdict(long)

    for result in _calculate_page_result(user, test, date):
        page = Page.objects.get(id=result['page'])
        for tr in Tracked.objects.all():          
            if tr.page.id == page.id or is_page_under(tr.page, page):

                total[tr.page.id] += result['total']
                correct[tr.page.id] += result['correct']
    
    results = {} 
    for tr in Tracked.objects.all():
        page = Page.objects.get(id=tr.page.id)
        if total[tr.page.id] != 0:
            procent = float(correct[tr.page.id])/float(total[tr.page.id])*100 
            procent = int(procent+0.5) 
            results[tr.page.id] = {"tracked":tr.page.id, 'page':page, 'procent':procent, 'correct':correct[tr.page.id], 'total':total[tr.page.id]}
        else: 
            procent = "No questions answered."    
            results[tr.page.id] = {"tracked":tr.page.id, 'page':page, 'correct':0, 'total':0}
   
    return results
